-module(couch_storage).

-include("definitions.hrl").
-include("..\\deps\\couchbeam\\include\\couchbeam.hrl").
-include("couch_constants.hrl").

-export([
  start/1,
  init/1,
  stop/0,
  get_all_test_ids/0,
  get_test/1,
  add_test/1,
  insert_samples/0,
  get_state/1,
  get_last_state_for_device/1,
  update_state/1,
  get_last_state_for_devices/1
]).


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%интерфейс
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

-spec start/1 :: (pid()) -> true.
start(ServerPid) ->
  true = register(couchStorage, spawn(couch_storage, init, [ServerPid])).

-spec init/1 :: (pid()) -> couchStorage_stopped.
init(ServerPid) ->
  {ok, DbTests, DbStates, DbAnswers, DbMessages} = init_couch_db(),
  ServerPid ! {ok, storage_started},
  event_loop(DbTests, DbStates, DbAnswers, DbMessages).

-spec init_couch_db/0 :: () -> {ok, couchbeam:db(), couchbeam:db(), couchbeam:db(), couchbeam:db()}.
init_couch_db() ->
  couchbeam:start(),
  Options = [],
  Server = couchbeam:server_connection(?COUCH_URL, Options),
  {ok, DbTestsOld} = couchbeam:open_or_create_db(Server, ?TEST_DB_NAME, Options),
  {ok, DbStatesOld} = couchbeam:open_or_create_db(Server, ?STATE_DB_NAME, Options),
  {ok, DbAnswersOld} = couchbeam:open_or_create_db(Server, ?ANSWERS_DB_NAME, Options),
  {ok, DbMessagesOld} = couchbeam:open_or_create_db(Server, ?MESSAGES_DB_NAME, Options),

  couchbeam:delete_db(DbTestsOld),
  couchbeam:delete_db(DbStatesOld),
  couchbeam:delete_db(DbAnswersOld),
  couchbeam:delete_db(DbMessagesOld),

  {ok, DbTests} = couchbeam:open_or_create_db(Server, ?TEST_DB_NAME, Options),
  {ok, DbStates} = couchbeam:open_or_create_db(Server, ?STATE_DB_NAME, Options),
  {ok, DbAnswers} = couchbeam:open_or_create_db(Server, ?ANSWERS_DB_NAME, Options),
  {ok, DbMessages} = couchbeam:open_or_create_db(Server, ?MESSAGES_DB_NAME, Options),

  add_views(DbMessages, DbAnswers),


  {ok, DbTests, DbStates, DbAnswers, DbMessages}.

add_views(DbMessages, DbAnswers) ->
  DesignDoc = {[
    {<<"_id">>, <<"_design/messages_ids">>},
    {<<"language">>, <<"javascript">>},
    {<<"views">>,
      {[
        {<<?MESSAGES_IDS_VIEW>>,
          {[{<<"map">>,
            <<"function (doc){\n emit(doc.testId, {_id:doc._id, out:doc.out});\n}">>
          }]}
        }
      ]}
    }
  ]},
  {ok, _} = couchbeam:save_doc(DbMessages, DesignDoc),

  DesignDoc1 = {[
    {<<"_id">>, <<"_design/answers_ids">>},
    {<<"language">>, <<"javascript">>},
    {<<"views">>,
      {[
        {<<?ANSWERS_IDS_VIEW>>,
          {[{<<"map">>,
            <<"function (doc){\n emit(doc.stateId, {_id:doc._id, order:doc.order});\n}">>
          }]}
        }
      ]}
    }
  ]},
  {ok, _} = couchbeam:save_doc(DbAnswers, DesignDoc1).

-spec stop/0 :: () -> ok.
stop() ->
  try
    couchStorage ! {stopCouch, self()},
    receive
      couchStorage_stopped ->
        ok
    end
  catch
    error :_ ->
      ok
  end,
  ok.

-spec get_all_test_ids() -> [integer()].
get_all_test_ids() ->
  couchStorage ! {all, self()},
  receive
    {testIds, KeyList} -> KeyList
  end.

-spec get_test(test_id()) -> test().
get_test(TestId) ->
  couchStorage ! {get, TestId, self()},
  receive
    {yourTest, Test} -> Test
  end.

-spec add_test(test()) -> integer().
add_test(Test) ->
  couchStorage ! {put, Test, self()},
  Rez =
    receive {nextId, Id} when is_integer(Id) -> Id
    end,
  Rez.

-spec insert_samples() -> ok.
insert_samples() ->
  InsLambda = fun(Test) ->
    add_test(Test)
  end,
  Samples = storage:get_samples(),
  lists:foreach(InsLambda, Samples).


-spec get_state/1 :: (state_id()) -> state().
get_state(StateId) ->
  couchStorage ! {getState, StateId, self()},
  receive
    State -> State
  end.

-spec get_last_state_for_device/1 :: (device_id()) -> state().
get_last_state_for_device(DeviceId) ->
  couchStorage ! {getLastForDevice, DeviceId, self()},
  receive
    {restored, State} ->
      State
  end.

-spec get_last_state_for_devices/1 :: (list(device_id())) -> list(state()).
get_last_state_for_devices(DeviceIds) ->
  couchStorage ! {getLastForDevices, DeviceIds, self()},
  receive
    {restored_list, States} ->
      States
  end.


-spec update_state/1 :: (state()) -> ok.
update_state(State) ->
  couchStorage ! {updateState, State, self()},
  ok.

-spec event_loop/4 :: (couchbeam:db(), couchbeam:db(), couchbeam:db(), couchbeam:db()) -> couchStorage_stopped.
event_loop(DbTests, DbStates, DbAnswers, DbMessages) ->
  receive

  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  %% testCases messages
  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    {get, TestId, From} when is_pid(From), TestId > 0 ->
      get_test(DbTests, DbMessages, TestId, From),
      event_loop(DbTests, DbStates, DbAnswers, DbMessages);

    {put, Test, From} when is_pid(From) ->
      add_test(DbTests, DbMessages, Test, From),
      event_loop(DbTests, DbStates, DbAnswers, DbMessages);

    {all, From} when is_pid(From) ->
      get_all_tests_ids(DbTests, From),
      event_loop(DbTests, DbStates, DbAnswers, DbMessages);

  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  %% stateRecords messages
  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

    {updateState, State, From} when is_pid(From) ->
      update_state(DbStates, DbAnswers, State, From),
      event_loop(DbTests, DbStates, DbAnswers, DbMessages);

    {getState, StateId, From} when is_pid(From), StateId > 0 ->
      get_state(DbStates, DbAnswers, StateId, From),
      event_loop(DbTests, DbStates, DbAnswers, DbMessages);

    {getLastForDevice, DeviceId, From} when is_pid(From), DeviceId > 0 ->
      Result = get_last_state_for_device(DbStates, DbAnswers, DeviceId),
      From ! {restored, Result},
      event_loop(DbTests, DbStates, DbAnswers, DbMessages);

    {getLastForDevices, DeviceIdList, From} when is_pid(From), is_list(DeviceIdList) ->
      States = lists:map(
        fun(DeviceId) ->
          get_last_state_for_device(DbStates, DbAnswers, DeviceId)
        end,
        DeviceIdList),
      From ! {restored_list, States},
      event_loop(DbTests, DbStates, DbAnswers, DbMessages);


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% stop
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    {stopCouch, From} when is_pid(From) ->
      From ! couchStorage_stopped
  end.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%реализация
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

-spec get_all_tests_ids/2 :: (db(), pid()) -> ok.
get_all_tests_ids(DbTests, From) ->
  Options = [include_docs],
  ParseId = fun(Doc) ->
    Val = get_value_by_key(Doc, "id"),
    {ResId, _} = string:to_integer(Val),
    ResId
  end,

  Docs = case couchbeam_view:all(DbTests, Options) of
           {error, R} ->
             error(R);

           {ok, Docs1, T} ->
             io:format("~p~n", [T]),
             Docs1;

           {ok, AllDocs} ->
             AllDocs
         end,

  Res = lists:map(ParseId, Docs),
  From ! {testIds, Res},
  ok.

-spec get_test/4 :: (db(), db(), test_id(), pid()) -> ok.
get_test(DbTests, DbMessages, TestId, From) ->
  Id = list_to_bitstring(integer_to_list(TestId)),
  Output = get_output_test(Id, DbMessages),
  Input = get_input_test(Id, DbMessages),
  {ok, Doc} = couchbeam:open_doc(DbTests, Id),

  DeviceId = get_value_by_key(Doc, "deviceId"),
  Descr = get_value_by_key(Doc, "description"),

  {DeviceIdInt, _} = string:to_integer(DeviceId),
  Test = #testCase{id = TestId, deviceId = DeviceIdInt, description = Descr, dataField = Input, desiredAnswer = Output},
  From ! {yourTest, Test},
  ok.

-spec add_test/4 :: (db(), db(), test(), pid()) -> ok.
add_test(DbTests, DbMessages, Test, From) ->
  NextId = storage:get_next_id(),
  Idb = list_to_bitstring(integer_to_list(NextId)),
  Devb = list_to_bitstring(integer_to_list(Test#testCase.deviceId)),
  Descb = list_to_bitstring(Test#testCase.description),
  Doc = {[
    {<<"_id">>, Idb},
    {<<"deviceId">>, Devb},
    {<<"description">>, Descb}
  ]},
  {ok, _} = couchbeam:save_doc(DbTests, Doc),
  insert_messages(DbMessages, NextId, false, Test#testCase.dataField),
  insert_messages(DbMessages, NextId, true, Test#testCase.desiredAnswer),
  From ! {nextId, NextId},
  ok.

-spec get_state/4 :: (db(), db(), state_id(), pid()) -> state().
get_state(DbStates, DbAnswers, StateId, From) ->
  StateIdStr = list_to_bitstring(integer_to_list(StateId)),
  X = couchbeam:open_doc(DbStates, StateIdStr),
  State = case X of
            {ok, Doc} ->
              Compl =
                case get_value_by_key(Doc, "completed") of
                  "1" -> true;
                  _ -> false
                end,
              {Device, _} = string:to_integer(get_value_by_key(Doc, "deviceId")),
              {Fails, _} = string:to_integer(get_value_by_key(Doc, "fails")),
              {Sends, _} = string:to_integer(get_value_by_key(Doc, "sends")),
              {TestId, _} = string:to_integer(get_value_by_key(Doc, "testId")),
              #stateRecord{id = StateId, sends = Sends, deviceId = Device, completed = Compl, fails = Fails, testId = TestId};

            {error, _} ->
              error("state wa not gotten in couch_storage get_state~n")
%%               #stateRecord{id = -1, completed = false, deviceId = -1, fails = 0, sends = 0, answer = []}

          end,
  Answers = get_answers_by_state(DbAnswers, StateIdStr),
  Result = State#stateRecord{answer = Answers},
  From ! Result,
  Result.


-spec update_state/4 :: (db(), db(), state(), pid()) -> ok.
update_state(DbStates, DbAnswers, State, _) ->
  StateIdStr = list_to_bitstring(integer_to_list(State#stateRecord.id)),

  Compl = case State#stateRecord.completed of
            true -> <<"1">>;
            false -> <<"0">>
          end,
  Device = list_to_bitstring(integer_to_list(State#stateRecord.deviceId)),
  Fails = list_to_bitstring(integer_to_list(State#stateRecord.fails)),
  Sends = list_to_bitstring(integer_to_list(State#stateRecord.sends)),
  TestId = list_to_bitstring(integer_to_list(State#stateRecord.testId)),

  Doc = {
    [
      {<<"_id">>, StateIdStr},
      {<<"completed">>, Compl},
      {<<"deviceId">>, Device},
      {<<"fails">>, Fails},
      {<<"sends">>, Sends},
      {<<"testId">>, TestId}
    ]
  },
  X = couchbeam:open_doc(DbStates, StateIdStr),

  case X of
    {error, _} ->
      {ok, _} = couchbeam:save_doc(DbStates, Doc),
      insert_answers(DbAnswers, StateIdStr, State#stateRecord.answer, 1);
    {ok, DocOld} ->
      {SendsOld, _} = string:to_integer(get_value_by_key(DocOld, "sends")),
      couchbeam:save_doc(DbStates, Doc),
      ToSend = lists:nthtail(SendsOld, State#stateRecord.answer),
      insert_answers(DbAnswers, StateIdStr, ToSend, SendsOld + 1)
  end,
  ok.


-spec get_last_state_for_device/3 :: (db(), db(), device_id()) -> state().
get_last_state_for_device(DbStates, DbAnswers, DeviceId) ->
  Options = [include_docs],
  Docs = case couchbeam_view:all(DbStates, Options) of
           {error, Reason} ->
             error(Reason);

           {ok, Ds, T} ->
             io:format("~p~n", [T]),
             Ds;

           {ok, AllDocs} ->
             AllDocs
         end,

  ParseId = fun(Doc) ->
    Val = get_value_by_key(Doc, "id"),
    {ResId, _} = string:to_integer(Val),
    ResId
  end,

  Res = lists:sort(
    fun(Fst, Snd) when is_integer(Fst), is_integer(Snd) ->
      Fst > Snd
    end,

    lists:map(ParseId, Docs)),

  State = get_state_by_device(DbStates, Res, list_to_bitstring(integer_to_list(DeviceId))),
  StateId = State#stateRecord.id,
  StateIdStr = list_to_bitstring(integer_to_list(StateId)),
  Answers = get_answers_by_state(DbAnswers, StateIdStr),
  State#stateRecord{answer = Answers}.

-spec get_state_by_device/3 :: (db(), [integer()], device_id()) -> state().
get_state_by_device(_, [], Device) ->
  #stateRecord{id = -1, deviceId = Device, testId = -1};

get_state_by_device(DbStates, [Id | Ids], Device) ->
  IdStr = list_to_bitstring(integer_to_list(Id)),
  {ok, Doc} = couchbeam:open_doc(DbStates, IdStr),

  ValFromDb = list_to_bitstring(get_value_by_key(Doc, "deviceId")),
  case Device of
    ValFromDb ->
      Compl =
        case get_value_by_key(Doc, "completed") of
          "1" -> true;
          _ -> false
        end,
      {Device, _} = string:to_integer(get_value_by_key(Doc, "deviceId")),
      {Fails, _} = string:to_integer(get_value_by_key(Doc, "fails")),
      {Sends, _} = string:to_integer(get_value_by_key(Doc, "sends")),
      {TestId, _} = string:to_integer(get_value_by_key(Doc, "testId")),

      #stateRecord{id = Id, sends = Sends, deviceId = Device, completed = Compl, fails = Fails, testId = TestId};

    N ->
      io:format(bitstring_to_list(N)),
      io:nl(),
      get_state_by_device(DbStates, Ids, Device)
  end.

-spec insert_messages/4 :: (db(), pos_integer(), boolean(), [byte()]) -> no_return().
insert_messages(DbMessages, TestId, IsAnswer, MessagesBin) ->
  Count = erlang:length(MessagesBin),
  IdsList = lists:map(fun(_) -> list_to_bitstring(integer_to_list(storage:get_next_id())) end, MessagesBin),
  OrdersStrings = [list_to_bitstring(integer_to_list(Int)) || Int <- lists:seq(1, Count)],
  Out = case IsAnswer of
          true -> <<"1">>;
          _ -> <<"0">>
        end,
  Testb = list_to_bitstring(integer_to_list(TestId)),
  add_messages(IdsList, OrdersStrings, Out, Testb, MessagesBin, DbMessages).

-spec add_messages/6 :: ([string()], [string()], <<_:8>>, string(), [binary()], db()) -> ok.
add_messages([], _, _, _, _, _) ->
  ok;

add_messages([Id | Ids], [Order | Orders], IsAnswer, TestId, [MsgBin | Messages], DbMessages)
  ->
  Doc = {[
    {<<"_id">>, Id},
    {<<"order">>, Order},
    {<<"out">>, IsAnswer},
    {<<"testId">>, TestId}
  ]},

  {ok, DocNew} = couchbeam:save_doc(DbMessages, Doc),
  Rev = couchbeam_doc:get_rev(DocNew),
  Options = [{rev, Rev}],

  X = couchbeam:put_attachment(DbMessages, Id, ?MESSAGE_ATTACHMENT, MsgBin, Options),
  case X of
    {ok, _} -> ok;
    {error, conflict} -> io:format("conflict ~w~n", [Id])
  end,
  add_messages(Ids, Orders, IsAnswer, TestId, Messages, DbMessages).


-spec get_output_test/2 :: (bitstring(), db()) -> [<<_:64>>].
get_output_test(TestId, DbMessages) ->
  Ids = get_message_docs_ids(TestId, DbMessages),
  get_messages(DbMessages, Ids).


-spec get_message_docs_ids/2 :: (bitstring(), db()) -> [string()].
get_message_docs_ids(TestId, DbMessages) ->
  ViewName = {?MESSAGES_DESIGN_ID, ?MESSAGES_IDS_VIEW},
  Options = [{key, TestId}],
  AnswerDb = couchbeam_view:fetch(DbMessages, ViewName, Options),
  OutState = "1",
  case AnswerDb of
    {'error', R} ->
      io:format("get_message_docs_ids, ~p~n", [TestId]),
      io:format(R),
      error(R);

    {ok, Docs, T} ->
      io:format("~p~n", [T]),
      get_ids_by_out(Docs, OutState);

    {ok, MessageDocs} ->
      %[{<<2 bytes>>,<<2 bytes>>},{<<3 bytes>>,<<1 byte>>},{<<5 bytes>>,<<2 bytes>>}]
      get_ids_by_out(MessageDocs, OutState)
  end.

-spec get_input_test/2 :: (binary(), db()) -> [binary()].
get_input_test(TestId, DbMessages) ->
  ViewName = {?MESSAGES_DESIGN_ID, ?MESSAGES_IDS_VIEW},
  Options = [{key, TestId}],
  Docs = case couchbeam_view:fetch(DbMessages, ViewName, Options) of
           {ok, MessageDocs} ->
             MessageDocs;

           {error, Reason} ->
             error(Reason);

           {ok, Ds, T} ->
             io:format("~p~n", [T]),
             Ds
         end,
  OutState = "0",
  %[{<<2 bytes>>,<<2 bytes>>},{<<3 bytes>>,<<1 byte>>},{<<5 bytes>>,<<2 bytes>>}]
  Ids = get_ids_by_out(Docs, OutState),
  get_messages(DbMessages, Ids).


-spec get_messages/2 :: (db(), [string()]) -> [binary()].
get_messages(DbMessages, Ids) ->

  AttachmentLoader = fun(IdStr) ->
    X = couchbeam:fetch_attachment(DbMessages, IdStr, ?MESSAGE_ATTACHMENT),
    case X of
      {ok, Message} when is_atom(Message) -> atom_to_binary(Message, unicode);

      {ok, MessageBin} when is_binary(MessageBin) -> MessageBin;

      {error, R} ->
        io:format(R),
        io:nl(),
        <<3:2, 0:8, 1:12, 1:32, 3:2, 2:8>>
    end
  end,

  lists:map(AttachmentLoader, Ids).


-spec insert_answers/4 :: (db(), string(), [binary()], integer()) -> ok.
insert_answers(DbAnswers, StateId, AnswersList, StartOrder) ->
  Count = length(AnswersList),
  case Count of
    0 -> ok;
    N ->
      IdsList = lists:map(fun(_) -> list_to_bitstring(integer_to_list(storage:get_next_id())) end, AnswersList),
      OrderList = [list_to_bitstring(integer_to_list(B)) || B <- lists:seq(StartOrder, StartOrder + N - 1)],
      insert_answer_list(DbAnswers, IdsList, StateId, OrderList, AnswersList)
  end.

-spec insert_answer_list/5 :: (db(), [string()], string(), [string()], [binary()]) -> ok.
insert_answer_list(_, [], _, _, _) ->
  ok;

insert_answer_list(DbAnswers, [Id | Ids], StateId, [Order | Orders], [Ans | AnswersList]) ->
  Doc = {[
    {<<"_id">>, Id},
    {<<"order">>, Order},
    {<<"stateId">>, StateId}
  ]},

  {ok, DocNew} = couchbeam:save_doc(DbAnswers, Doc),
  Rev = couchbeam_doc:get_rev(DocNew),
  Options = [{rev, Rev}],
  X = couchbeam:put_attachment(DbAnswers, Id, ?ANSWER_ATTACHMENT, Ans, Options),
  case X of
    {ok, _} -> ok;
    {error, conflict} -> io:format("conflict ~w~n", [Id])
  end,
  insert_answer_list(DbAnswers, Ids, StateId, Orders, AnswersList).

-spec get_ids_by_out/2 :: ([doc()], string()) -> [string()].
get_ids_by_out(List, OutState) ->
  lists:filtermap(

    fun(El) ->
      Id = get_value_by_key(El, "id"),
      IsOut = get_value_by_key(El, "out"),
      case IsOut of
        OutState -> {true, Id};
        _ -> false
      end
    end,

    List).

-spec get_answers_by_state/2 :: (db(), string()) -> [binary()].
get_answers_by_state(DbAnswers, State) ->

  ViewName = {?ANSWERS_DESIGN_ID, ?ANSWERS_IDS_VIEW},
  Options = [{key, State}],
  Answer = couchbeam_view:fetch(DbAnswers, ViewName, Options),
  AnswersDocs = case Answer of
                  {error, R} ->
                    error(R);

                  {ok, Ds, T} ->
                    io:format("~p~n", [T]),
                    Ds;

                  {ok, Docs} ->
                    Docs
                end,

%[{<<2 bytes>>,<<2 bytes>>},{<<3 bytes>>,<<1 byte>>},{<<5 bytes>>,<<2 bytes>>}]
  Ids = lists:map(
    fun(El) -> get_value_by_key(El, "id") end,
    AnswersDocs),

  MessageList = lists:map(
    fun(IdStr) ->
      X = couchbeam:fetch_attachment(DbAnswers, IdStr, ?ANSWER_ATTACHMENT),
      case X of
        {ok, Message} -> Message;
        {error, R1} ->
          io:format(R1),
          io:nl(),
          <<3:2, 0:8, 1:12, 1:32, 3:2, 2:8>>
      end
    end,

    Ids),
  MessageList.

-spec get_value_by_key/2 :: (doc(), string()) -> string().
get_value_by_key(Doc, Key) ->
  KeyBinary = list_to_bitstring(Key),

  FindTuple = fun(Tuple) ->
    {Keyb, Tk} = Tuple,
    P = case Keyb of
    %% для вставки "_id", для чтения "id". Как же весело
          KeyBinary -> true;

          T ->
            case Tk of
              {[{_, _}]} ->
                false;
              _ when T == <<"value">> ->
                true;

              _ -> false
            end
        end,
    P
  end,

  {PropList} = Doc,
  case lists:filter(FindTuple, PropList) of

    [{_, Val} | _] ->
      if
        is_bitstring(Val) ->
          bitstring_to_list(Val);
        true ->
          get_value_by_key(Val, Key)
      end
  end.